using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections;

namespace SceneEditor.UI
{
	public class MultiSelectTreeview : TreeView
	{
        /// <summary>
        ///  Create a node sorter that implements the IComparer interface.
        /// </summary>
        public class NodeSorter : IComparer
        {
            // compare between two tree nodes
            public int Compare(object thisObj, object otherObj)
            {
                TreeNode thisNode = thisObj as TreeNode;
                TreeNode otherNode = otherObj as TreeNode;

                // Compare the types of the tags, returning the difference.
                if (thisNode.ImageIndex != otherNode.ImageIndex)
                {
                    return thisNode.ImageIndex.CompareTo(otherNode.ImageIndex);
                }
                else
                {
                    //alphabetically sorting
                    return thisNode.Text.CompareTo(otherNode.Text);
                }
            }
        } 

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

        private const int MAPSIZE = 128;

        private string nodeMap;
        private StringBuilder nodeMapBuilder = new StringBuilder(MAPSIZE);

        // Note we use the new keyword to Hide the native treeview's SelectedNode property.
        private TreeNode selectedNode;
		private List<TreeNode> selectedNodes = null;		

		public List<TreeNode> SelectedNodes
		{
			get
			{
				return selectedNodes;
			}
			set
			{
				ClearSelectedNodes();
				if( value != null )
				{
					foreach( TreeNode node in value )
					{
						ToggleNode( node, true );
					}
				}
			}
		}
		public new TreeNode SelectedNode
		{
            get { return selectedNode; }
            set
            {
                ClearSelectedNodes();
                if (value != null)
                {
                    SelectNode(value);
                }
            }
		}

        public event TreeNodeMovedEventHandler NodeMoved;

		public MultiSelectTreeview()
		{
			selectedNodes = new List<TreeNode>();
			base.SelectedNode = null;

            base.TreeViewNodeSorter = new NodeSorter();
		}

		protected override void OnGotFocus( EventArgs e )
		{
			// Make sure at least one node has a selection
			// this way we can tab to the ctrl and use the 
			// keyboard to select nodes
			try
			{
				if( selectedNode == null && this.TopNode != null )
				{
					ToggleNode( this.TopNode, true );
				}

				base.OnGotFocus( e );
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnMouseDown( MouseEventArgs e )
		{
			// If the user clicks on a node that was not
			// previously selected, select it now.
			try
			{
				base.SelectedNode = null;

				TreeNode node = this.GetNodeAt( e.Location );

				if( node != null )
				{
					int leftBound = node.Bounds.X; // - 20; // Allow user to click on image
					int rightBound = node.Bounds.Right + 10; // Give a little extra room

					if( e.Location.X > leftBound && e.Location.X < rightBound )
					{
						if( ModifierKeys == Keys.None && ( selectedNodes.Contains( node ) ) )
						{
							// Potential Drag Operation
							// Let Mouse Up do select
						}
						else
						{							
							SelectNode( node );
						}
					}
				}

				base.OnMouseDown( e );
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnMouseUp( MouseEventArgs e )
		{
			// If the clicked on a node that WAS previously
			// selected then, reselect it now. This will clear
			// any other selected nodes. e.g. A B C D are selected
			// the user clicks on B, now A C & D are no longer selected.
			try
			{
				// Check to see if a node was clicked on 
				TreeNode node = this.GetNodeAt( e.Location );
				if( node != null )
				{
					if( ModifierKeys == Keys.None && selectedNodes.Contains( node ) )
					{
						int leftBound = node.Bounds.X; // -20; // Allow user to click on image
						int rightBound = node.Bounds.Right + 10; // Give a little extra room
						if( e.Location.X > leftBound && e.Location.X < rightBound )
						{
							SelectNode( node );
						}
					}
				}

				base.OnMouseUp( e );
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnItemDrag( ItemDragEventArgs e )
		{
			// If the user drags a node and the node being dragged is NOT
			// selected, then clear the active selection, select the
			// node being dragged and drag it. Otherwise if the node being
			// dragged is selected, drag the entire selection.
			try
			{
				TreeNode node = e.Item as TreeNode;

				if( node != null )
				{
					if( !selectedNodes.Contains( node ) )
					{
						SelectSingleNode( node );
						ToggleNode( node, true );
					}
				}

                if (AllowDrop)
                {
                    if ((e.Item as TreeNode).Tag == null ||
                        (e.Item as TreeNode).Tag != null && (bool)(e.Item as TreeNode).Tag == false)
                    {
                        DoDragDrop(e.Item, DragDropEffects.Move);
                    }
                }

				base.OnItemDrag(e);
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnBeforeSelect( TreeViewCancelEventArgs e )
		{
			// Never allow base.SelectedNode to be set!
			try
			{
				base.SelectedNode = null;
				e.Cancel = true;

				base.OnBeforeSelect( e );
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnAfterSelect( TreeViewEventArgs e )
		{
			// Never allow base.SelectedNode to be set!
			try
			{
				base.OnAfterSelect( e );
				base.SelectedNode = null;
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
		}

		protected override void OnKeyDown( KeyEventArgs e )
		{
			// Handle all possible key strokes for the control.
			// including navigation, selection, etc.

			base.OnKeyDown( e );

			if( e.KeyCode == Keys.ShiftKey ) return;

			//this.BeginUpdate();
			bool bShift = ( ModifierKeys == Keys.Shift );

			try
			{
				// Nothing is selected in the tree, this isn't a good state
				// select the top node
				if( selectedNode == null && this.TopNode != null )
				{
					ToggleNode( this.TopNode, true );
				}

				// Nothing is still selected in the tree, this isn't a good state, leave.
				if( selectedNode == null ) return;

				if( e.KeyCode == Keys.Left )
				{
					if( selectedNode.IsExpanded && selectedNode.Nodes.Count > 0 )
					{
						// Collapse an expanded node that has children
						selectedNode.Collapse();
					}
					else if( selectedNode.Parent != null )
					{
						// Node is already collapsed, try to select its parent.
						SelectSingleNode( selectedNode.Parent );
					}
				}
				else if( e.KeyCode == Keys.Right )
				{
					if( !selectedNode.IsExpanded )
					{
						// Expand a collpased node's children
						selectedNode.Expand();
					}
					else
					{
						// Node was already expanded, select the first child
						SelectSingleNode( selectedNode.FirstNode );
					}
				}
				else if( e.KeyCode == Keys.Up )
				{
					// Select the previous node
					if( selectedNode.PrevVisibleNode != null )
					{
						SelectNode( selectedNode.PrevVisibleNode );
					}
				}
				else if( e.KeyCode == Keys.Down )
				{
					// Select the next node
					if( selectedNode.NextVisibleNode != null )
					{
						SelectNode( selectedNode.NextVisibleNode );
					}
				}
				else if( e.KeyCode == Keys.Home )
				{
					if( bShift )
					{
						if( selectedNode.Parent == null )
						{
							// Select all of the root nodes up to this point 
							if( this.Nodes.Count > 0 )
							{
								SelectNode( this.Nodes[0] );
							}
						}
						else
						{
							// Select all of the nodes up to this point under this nodes parent
							SelectNode( selectedNode.Parent.FirstNode );
						}
					}
					else
					{
						// Select this first node in the tree
						if( this.Nodes.Count > 0 )
						{
							SelectSingleNode( this.Nodes[0] );
						}
					}
				}
				else if( e.KeyCode == Keys.End )
				{
					if( bShift )
					{
						if( selectedNode.Parent == null )
						{
							// Select the last ROOT node in the tree
							if( this.Nodes.Count > 0 )
							{
								SelectNode( this.Nodes[this.Nodes.Count - 1] );
							}
						}
						else
						{
							// Select the last node in this branch
							SelectNode( selectedNode.Parent.LastNode );
						}
					}
					else
					{
						if( this.Nodes.Count > 0 )
						{
							// Select the last node visible node in the tree.
							// Don't expand branches incase the tree is virtual
							TreeNode ndLast = this.Nodes[0].LastNode;
							while( ndLast.IsExpanded && ( ndLast.LastNode != null ) )
							{
								ndLast = ndLast.LastNode;
							}
							SelectSingleNode( ndLast );
						}
					}
				}
				else if( e.KeyCode == Keys.PageUp )
				{
					// Select the highest node in the display
					int nCount = this.VisibleCount;
					TreeNode ndCurrent = selectedNode;
					while( ( nCount ) > 0 && ( ndCurrent.PrevVisibleNode != null ) )
					{
						ndCurrent = ndCurrent.PrevVisibleNode;
						nCount--;
					}
					SelectSingleNode( ndCurrent );
				}
				else if( e.KeyCode == Keys.PageDown )
				{
					// Select the lowest node in the display
					int nCount = this.VisibleCount;
					TreeNode ndCurrent = selectedNode;
					while( ( nCount ) > 0 && ( ndCurrent.NextVisibleNode != null ) )
					{
						ndCurrent = ndCurrent.NextVisibleNode;
						nCount--;
					}
					SelectSingleNode( ndCurrent );
				}
				else
				{
					// Assume this is a search character a-z, A-Z, 0-9, etc.
					// Select the first node after the current node that 
					// starts with this character
					string sSearch = ( (char) e.KeyValue ).ToString();

					TreeNode ndCurrent = selectedNode;
					while( ( ndCurrent.NextVisibleNode != null ) )
					{
						ndCurrent = ndCurrent.NextVisibleNode;
						if( ndCurrent.Text.StartsWith( sSearch ) )
						{
							SelectSingleNode( ndCurrent );
							break;
						}
					}
				}
			}
			catch( Exception ex )
			{
				HandleException( ex );
			}
			finally
			{
				this.EndUpdate();
			}
		}

        protected override void OnDragDrop(DragEventArgs e)
        {
            if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", false) && this.nodeMap != "")
            {
                string[] nodeIndices = this.nodeMap.Split('|');
                TreeNodeCollection insertCollection = Nodes;

                TreeNode parentNode = Nodes[0];

                for (int i = 0; i < nodeIndices.Length - 1; i++)
                {
                    parentNode = insertCollection[Int32.Parse(nodeIndices[i])];
                    insertCollection = insertCollection[Int32.Parse(nodeIndices[i])].Nodes;
                }

                if (insertCollection != null)
                {
                    List<TreeNode> newNodes = new List<TreeNode>();

                    for (int i = selectedNodes.Count - 1; i >= 0; i--)
                    {
                        selectedNodes[i].ForeColor = this.ForeColor;
                        selectedNodes[i].BackColor = this.BackColor;
                        
                        if (parentNode == selectedNodes[i])
                        {
                            continue;
                        }

                        TreeNode newNode = (TreeNode)selectedNodes[i].Clone();

                        insertCollection.Insert(Int32.Parse(nodeIndices[nodeIndices.Length - 1]), newNode);
                        newNodes.Add(newNode);

                        selectedNodes[i].Remove();
                    }

                    if (NodeMoved != null && newNodes.Count > 0)
                    {
                        NodeMoved(this, new TreeNodeMovedEventArgs(newNodes));
                    }

                    parentNode.Expand();
                    ClearSelectedNodes();
                }
            }

            ClearSelectedNodes();

            base.OnDragDrop(e);
        }

        protected override void OnDragOver(DragEventArgs e)
        {
            Scroll();
            Point cursorPosition = PointToClient(Cursor.Position);

            TreeNode nodeOver = GetNodeAt(cursorPosition);
            TreeNode nodeMoving = (TreeNode)e.Data.GetData("System.Windows.Forms.TreeNode");

            bool insertIntoSelf = true;

            for (int i = 0; i < selectedNodes.Count; i++)
            {
                if (selectedNodes[i] != nodeOver)
                {
                    insertIntoSelf = false;
                }
            }

            // A bit long, but to summarize, process the following code only if the nodeover is null
            // and either the nodeover is not the same thing as nodemoving UNLESSS nodeover happens
            // to be the last node in the branch (so we can allow drag & drop below a parent branch)
            if (nodeOver != null && (insertIntoSelf == false || (nodeOver.Parent != null && nodeOver.Index == (nodeOver.Parent.Nodes.Count - 1))))
            {
                int offsetY = cursorPosition.Y - nodeOver.Bounds.Top;

                TreeNode parentNode = nodeOver;

                // Make sure that you are not trying to make a node a child of itself.
                while (parentNode.Parent != null)
                {
                    if (parentNode.Parent == nodeMoving)
                    {
                        this.nodeMap = "";
                        return;
                    }

                    parentNode = parentNode.Parent;
                }

                // Image index of 1 is the non-folder icon
                if (nodeOver.ImageIndex == 1)
                {
                    bool shouldReturn = HandleNodeOverStandard(nodeOver, offsetY < (nodeOver.Bounds.Height / 2));

                    if (shouldReturn)
                    {
                        return;
                    }
                }
                else
                {
                    // The node being dragged is above the center of the node that it is over.
                    if (offsetY < (nodeOver.Bounds.Height / 3))
                    {
                        // Store the placeholder info into a pipe delimited string
                        SetNewNodeMap(nodeOver, false);

                        // Only redraw the control if the node being dragged has moved
                        // into a new state (e.g Was above node, now below)
                        if (SetMapsEqual() == true)
                        {
                            return;
                        }

                        // Clear placeholders above and below
                        this.Refresh();
                        // Draw the placeholders
                        DrawAddToFolderCursor(nodeOver);
                    }
                    else
                    {
                        // Prevent the node from being dragged onto itself
                        if (insertIntoSelf)
                        {
                            return;
                        }

                        // Store the placeholder info into a pipe delimited string
                        SetNewNodeMap(nodeOver, false);
                        nodeMapBuilder = nodeMapBuilder.Insert(nodeMapBuilder.Length, "|0");

                        if (SetMapsEqual() == true)
                        {
                            return;
                        }

                        // Clear placeholders above and below
                        this.Refresh();
                        // Draw the "add to folder" placeholder
                        DrawAddToFolderCursor(nodeOver);
                    }
                }
            }

            base.OnDragOver(e);
        }

        private bool HandleNodeOverStandard(TreeNode nodeOver, bool aboveNode)
        {
            Point cursorPosition = PointToClient(Cursor.Position);

            // The node being dragged is above the center of the node that it is over.
            if (aboveNode)
            {
                //Store the placeholder info into a pipe delimited string
                SetNewNodeMap(nodeOver, false);

                // Only redraw the control if the node being dragged has moved
                // into a new state (e.g Was above node, now below)
                if (SetMapsEqual() == true)
                {
                    return true;
                }

                Refresh();
                DrawDragCursor(nodeOver, null, false);
            }
            // The node being dragged is below the center of the node that it is over.
            else
            {
                TreeNode parentOfNodeOver = null;

                // If the node the mouse is over is the last node of the branch we should allow
                // the ability to drop the dragged node BELOW the parent node
                if (nodeOver.Parent != null && nodeOver.Index == (nodeOver.Parent.Nodes.Count - 1))
                {
                    if (cursorPosition.X < nodeOver.Bounds.Left)
                    {
                        parentOfNodeOver = nodeOver.Parent;

                        if (cursorPosition.X < (parentOfNodeOver.Bounds.Left - 
                            ImageList.Images[parentOfNodeOver.ImageIndex].Size.Width))
                        {
                            if (parentOfNodeOver.Parent != null)
                                parentOfNodeOver = parentOfNodeOver.Parent;
                        }
                    }
                }

                // Since we are in a special case here, use the ParentDragDrop node as the current "nodeover"
                SetNewNodeMap(parentOfNodeOver != null ? parentOfNodeOver : nodeOver, true);

                if (SetMapsEqual() == true)
                {
                    return true;
                }

                Refresh();
                DrawDragCursor(nodeOver, parentOfNodeOver, true);
            }

            return false;
        }

        protected override void OnDragEnter(DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        #region Helper Methods

        private void Scroll()
        {
            var pt = PointToClient(Cursor.Position);

            if ((pt.Y + 20) > Height)
            {
                // scroll down
                SendMessage(Handle, 277, (IntPtr)1, (IntPtr)0);
            }
            else if (pt.Y < 20)
            {
                // scroll up
                SendMessage(Handle, 277, (IntPtr)0, (IntPtr)0);
            }
        }

        public string GetGroupPathFromNode(TreeNode node, bool includeName)
        {
            TreeNode parent = node.Parent;
            string nodePath = includeName ? '|' + node.Name : string.Empty;

            while (parent != null)
            {
                nodePath = "|" + parent.Name + nodePath;
                parent = parent.Parent;
            }

            if (nodePath[0] == '|')
            {
                nodePath = nodePath.Remove(0, 1);
            }

            return nodePath;
        }

        public TreeNode GetNodeFromGroupPath(string path)
        {
            string[] groupPath = path.Split('|');

            TreeNodeCollection treeNodes = Nodes;

            for (int i = 0; i < groupPath.Length - 1; i++)
            {
                treeNodes = treeNodes[groupPath[i]].Nodes;
            }

            return treeNodes[groupPath[groupPath.Length - 1]];
        }
        public TreeNode GetNodeFromGroupPath(string path, string nodeKey)
        {
            string[] groupPath = path.Split('|');

            TreeNodeCollection treeNodes = Nodes;

            for (int i = 0; i < groupPath.Length; i++)
            {
                treeNodes = treeNodes[groupPath[i]].Nodes;
            }

            return treeNodes[nodeKey];
        }

		private void SelectNode( TreeNode node )
		{
			try
			{
				this.BeginUpdate();

                if (ModifierKeys == Keys.Control && selectedNode.Nodes.Count == 0)
                {
                    if (selectedNode.Parent == node.Parent && 
                        selectedNode.ImageIndex == node.ImageIndex)
                    {
                        bool bIsSelected = selectedNodes.Contains(node);
                        ToggleNode(node, !bIsSelected);
                    }
                }
                else if (ModifierKeys == Keys.Shift && selectedNode.Nodes.Count == 0)
                {
                    // Shift+Click selects nodes between the selected node and here.
                    TreeNode ndStart = selectedNode;
                    TreeNode ndEnd = node;

                    if (ndStart.Parent == ndEnd.Parent &&
                        selectedNode.ImageIndex == node.ImageIndex)
                    {
                        // Selected node and clicked node have same parent, easy case.
                        if (ndStart.Index < ndEnd.Index)
                        {
                            // If the selected node is beneath the clicked node walk down
                            // selecting each Visible node until we reach the end.
                            while (ndStart != ndEnd)
                            {
                                ndStart = ndStart.NextVisibleNode;
                                if (ndStart == null) break;
                                ToggleNode(ndStart, true);
                            }
                        }
                        else if (ndStart.Index == ndEnd.Index)
                        {
                            // Clicked same node, do nothing
                        }
                        else
                        {
                            // If the selected node is above the clicked node walk up
                            // selecting each Visible node until we reach the end.
                            while (ndStart != ndEnd)
                            {
                                ndStart = ndStart.PrevVisibleNode;
                                if (ndStart == null) break;
                                ToggleNode(ndStart, true);
                            }
                        }
                    }
                }
                else
                {
                    // Just clicked a node, select it
                    SelectSingleNode(node);
                }

				OnAfterSelect( new TreeViewEventArgs( selectedNode ) );
			}
			finally
			{
				this.EndUpdate();
			}
		}

		public void ClearSelectedNodes()
		{
			try
			{
				foreach( TreeNode node in selectedNodes )
				{
					node.BackColor = this.BackColor;
					node.ForeColor = this.ForeColor;
				}
			}
			finally
			{
				selectedNodes.Clear();
				selectedNode = null;
                base.SelectedNode = null;
			}
		}

		private void SelectSingleNode( TreeNode node )
		{
			if( node == null )
			{
				return;
			}

			ClearSelectedNodes();
			ToggleNode( node, true );
			node.EnsureVisible();
		}

		public void ToggleNode(TreeNode node, bool selectNode)
		{
			if( selectNode )
			{
				selectedNode = node;
				if( !selectedNodes.Contains( node ) )
				{
					selectedNodes.Add( node );
				}
				node.BackColor = SystemColors.Highlight;
				node.ForeColor = SystemColors.HighlightText;
			}
			else
			{
				selectedNodes.Remove( node );
				node.BackColor = this.BackColor;
				node.ForeColor = this.ForeColor;
			}
		}

		private void HandleException( Exception ex )
		{
			// Perform some error handling here.
			// We don't want to bubble errors to the CLR. 
			MessageBox.Show( ex.Message );
		}

        private void DrawDragCursor(TreeNode nodeOver, TreeNode parent, bool drawBelow)
        {
            using (Graphics g = CreateGraphics())
            {
                int imageWidth = ImageList.Images[nodeOver.ImageIndex].Size.Width + 8;

                int left = nodeOver.Bounds.Left - imageWidth;
                int right = ClientRectangle.Right - 3;

                int center = drawBelow ? nodeOver.Bounds.Bottom : nodeOver.Bounds.Top;

                if (parent != null)
                {
                    left = parent.Bounds.Left - (ImageList.Images[parent.ImageIndex].Size.Width + 8);
                }

                Point[] LeftTriangle = new Point[3]
                {
				    new Point(left, center - 3),
				    new Point(left, center + 3),
				    new Point(left + 3, center),
                };

                Point[] RightTriangle = new Point[3]
                {
			        new Point(right, center - 3),
			        new Point(right, center + 3),
			        new Point(right - 3, center),
                };

                g.FillPolygon(System.Drawing.Brushes.Black, LeftTriangle);
                g.FillPolygon(System.Drawing.Brushes.Black, RightTriangle);

                using (Pen linePen = new Pen(Color.Black, 1.0f))
                {
                    g.DrawLine(linePen, new Point(left + 2, center), new Point(right - 2, center));
                }
            }
        }
        private void DrawAddToFolderCursor(TreeNode nodeOver)
        {
            using (Graphics g = CreateGraphics())
            {
                int right = nodeOver.Bounds.Right + 6;
                int center = nodeOver.Bounds.Y + (nodeOver.Bounds.Height / 2);

                Point[] RightTriangle = new Point[3]
                {
					new Point(right, center + 4),
					new Point(right, center - 4),
					new Point(right - 4, center),
                };

                g.FillPolygon(System.Drawing.Brushes.Black, RightTriangle);
            }
        }

        private void SetNewNodeMap(TreeNode node, bool boolBelowNode)
        {
            nodeMapBuilder.Length = 0;

            if (boolBelowNode)
            {
                nodeMapBuilder.Insert(0, (int)node.Index + 1);
            }
            else
            {
                nodeMapBuilder.Insert(0, (int)node.Index);
            }

            TreeNode currentNode = node;

            while (currentNode.Parent != null)
            {
                currentNode = currentNode.Parent;

                if (nodeMapBuilder.Length == 0 && boolBelowNode == true)
                {
                    nodeMapBuilder.Insert(0, (currentNode.Index + 1) + "|");
                }
                else
                {
                    nodeMapBuilder.Insert(0, currentNode.Index + "|");
                }
            }
        }

        private bool SetMapsEqual()
        {
            if (this.nodeMapBuilder.ToString() == this.nodeMap)
            {
                return true;
            }
            else
            {
                this.nodeMap = this.nodeMapBuilder.ToString();
                return false;
            }
        }

		#endregion
	}
}
